package gui.event;

import gui.GameConstants;
import gui.PlayersMap;
import gui.ToolContainer;
import gui.drawers.RockDrawer;
import gui.drawers.ToolDrawer;

import java.awt.AlphaComposite;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Polygon;
import java.util.Vector;

import javax.swing.JLayeredPane;

import core.Physics;

import dal.MapStruct;
import dal.object.MapObject;
import dal.object.MapObject3D;
import dal.object.Movable;
import dal.object.Tool;
import dal.object.grounds.Water;

public class Collision {
	private Vector<MapObject> rocks;
	private Vector<MapObject> tools;
	private Vector<MapObject> grounds;
	private double dx, dy, vx, vy;
	private final int MAX, N;
	private boolean shift, collision;
	private JLayeredPane jLayers;
	private ToolDrawer td;
	private RockDrawer rd;
	private MapObject3D current;
	private Polygon hex;
	private int currentIndex;
	private ToolContainer tc;
	
	public Collision(MapStruct ms, PlayersMap pm, ToolContainer tc){
		this.rocks = ms.getRockVector();
		this.tools = ms.getToolVector();
		this.grounds = ms.getGroundsVector();
		this.MAX = rocks.size();
		this.N = (int) ms.getDimension().getWidth();
		this.jLayers = pm.getJLayeredPane();
		this.td = ((ToolDrawer) jLayers.getComponentsInLayer(GameConstants.TOOL_LAYER)[0]);
		this.rd = ((RockDrawer) jLayers.getComponentsInLayer(GameConstants.ROCK_LAYER)[0]);
		this.tc = tc;
	}
	
	/**
	 * @param x1, y1
	 * 	beginning point of the wall
	 * @param x2, y2
	 * 	ending point of the wall
	 */
	public void reflectVelocity(int x1, int y1, int x2, int y2) {
		double dx = x2 - x1;
		double dy = -(y2 - y1);
		
		double du = Math.sqrt(dy * dy + dx * dx);
		double ux = dx / du;
		double uy = dy / du;
		
		double s = (-vx * uy) + (-vy * ux);
		double qx = s * uy;
		double qy = s * ux;
		
		vx = 2 * qx + vx;
		vy = 2 * qy + vy;
	}
	
	/**
	 * occures when the ball collides withe a rock
	 */
	public void rockCollision(){
		double degree = getDegree();
		
		if(degree < 30 || degree >= 330) {
			vx = -vx;						
		} else if(degree < 90) {			
			reflectVelocity(hex.xpoints[0], hex.ypoints[0], hex.xpoints[1], hex.ypoints[1]);
		} else if(degree < 150) {
			reflectVelocity(hex.xpoints[5], hex.ypoints[5], hex.xpoints[0], hex.ypoints[0]);
		} else if(degree < 210) {			
			vx = -vx;
		} else if(degree < 270) {
			reflectVelocity(hex.xpoints[3], hex.ypoints[3], hex.xpoints[4], hex.ypoints[4]);			
		} else {
			reflectVelocity(hex.xpoints[2], hex.ypoints[2], hex.xpoints[3], hex.ypoints[3]);
		}
		
		Physics.setVelocity(vx, vy);
	}
	
	/**
	 * occures when the ball collides withe a movable object
	 */
	public void movableCollision(){
		double degree = getDegree();
		
		if(degree < 30 || degree >= 330) {		
			vx = -vx;			
			moveHorizontal(currentIndex, currentIndex - 1);
		} else if(degree < 90) {
			reflectVelocity(hex.xpoints[0], hex.ypoints[0], hex.xpoints[1], hex.ypoints[1]);
			moveVertical(currentIndex, shift ? (currentIndex + N - 1) : (currentIndex + N));			
		} else if(degree < 150) {
			reflectVelocity(hex.xpoints[5], hex.ypoints[5], hex.xpoints[0], hex.ypoints[0]);
			moveVertical(currentIndex, shift ? (currentIndex + N) : (currentIndex + N + 1));
		} else if(degree < 210) {
			vx = -vx;
			moveHorizontal(currentIndex, currentIndex + 1);
		} else if(degree < 270) {
			reflectVelocity(hex.xpoints[3], hex.ypoints[3], hex.xpoints[4], hex.ypoints[4]);			
			moveVertical(currentIndex, shift ? (currentIndex - N) : (currentIndex - N + 1));
		} else {
			reflectVelocity(hex.xpoints[2], hex.ypoints[2], hex.xpoints[3], hex.ypoints[3]);
			moveVertical(currentIndex, shift ? (currentIndex - N - 1) : (currentIndex - N));
		}
		
		Physics.setVelocity(vx, vy);
	}
	
	/**
	 * @param index
	 * 	initial index of the movable object
	 * @param next
	 * 	the destination index, it has to be a horizontal move, if it isn't nothing happens
	 */
	private void moveHorizontal(int index, int next) {
		if((next >= 0) && (next < MAX) && (rocks.get(next) == null) && (index / N == next / N)){
			MapObject ground = grounds.get(next);
			
			if(ground instanceof Water){
				Movable rock = (Movable) rocks.set(index, null);
				ground.setImage(rock.sink());
				grounds.set(next, rock.getGround());
			} else {
				rocks.set(next, rocks.set(index, null));
			}
			
			rd.repaintAll();
		}
	}
	
	/**
	 * @param index
	 * 	initial index of the movable object
	 * @param next
	 * 	the destination index, it has to be a vertical move, if it isn't nothing happens
	 */
	private void moveVertical(int index, int next) {
		if((next >= 0) && (next < MAX) && (rocks.get(next) == null) && (Math.abs(index / N - next / N) == 1)){
			MapObject ground = grounds.get(next);
			
			if(ground instanceof Water){
				Movable rock = (Movable) rocks.set(index, null);
				ground.setImage(rock.sink());
				grounds.set(next, rock.getGround());
			} else {
				rocks.set(next, rocks.set(index, null));
			}
			
			rd.repaintAll();
		}
	}
	
	/**
	 * @return
	 * 	returns the angle between the ball and the object collided with
	 */
	public double getDegree(){
		Point origo = current.getOrigo();		
		double degree = -Math.atan2((dy - origo.y), (dx - origo.x)) * (180d / Math.PI);
		
		return (degree < 0) ? (degree + 360) : degree;
	}
	
	/**
	 * @return
	 * 	returns true if the collision was strong enough
	 */
	public boolean isImpact(){
		return Math.sqrt(vx * vx + vy * vy) > 1000000d;
	}
	
	/**
	 * @param ind
	 * 	index that will be checked for collision
	 */
	private void check(int ind){
		if(ind >= 0 && ind < MAX){
			current = (MapObject3D) rocks.get(ind);
			
			if(current != null){
				hex = current.getHexagon();
				
				if(hex.contains(dx, dy)){
					collision = true;
					currentIndex = ind;
					
					if(isImpact()){
						current.impact(this);
					} else {
						current.touch(this);
					}
				}
			} else {
				current = (MapObject3D) tools.get(ind);
				
				if(current != null){
					hex = current.getHexagon();
					
					if(hex.contains(dx, dy)){
						collision = true;
						
						tc.addTool((Tool) tools.set(ind, null));
						Graphics2D gr = td.getGraphics2D();
						gr.setComposite(AlphaComposite.getInstance(AlphaComposite.CLEAR, 0.0F));
						gr.fillPolygon(hex);
					}
				}
			}
		}
	}
	
	/**
	 * @param shift
	 * 	represents the vertical index
	 * @param index
	 * 	vector index
	 * @param dx, dy
	 * 	ball current position
	 * @param vx, vy
	 * 	current velocity
	 * @return
	 * 	true if collision occurred
	 */
	public boolean collisionDetected(boolean shift, int index, double dx, double dy, double vx, double vy) {
		this.shift = shift;
		this.vx = vx;
		this.vy = vy;
		this.dx = dx;
		this.dy = dy;
		this.collision = false;
		
		check(index - N);
		check(index - 1);
		check(index + 1);
		check(index + N);
		
		if(shift){
			check(index - N + 1);
			check(index + N + 1);
		} else {
			check(index + N - 1);
			check(index - N - 1);
		}
		
		return collision;
	}
}
